//////////////////////////////////////////////////////////////////////////
#pragma once


#include "TweakUtils.hpp"
#include <Config/Config.hpp>
#include <Config/SerializersMath.hpp>
#include <vector>
#include <memory>
#include <functional>


#ifdef ENABLE_DEVBUILD

// Top level wrapper for the panel.  The intention is to have a standard
// object which controls if the panel is shown or not.  The content of
// the panel is recreated each time.  Also note, this uses initializer
// sequences which makes it easier to serialize the panel definition
// while also making in code definition relatively clean with all the
// oddball options available.
namespace TweakUtils
{
	class Panel
	{
		//
		Panel( const std::string& name );

	public:
		//
		Panel( const Panel& rhs );
		~Panel();

		//
		Panel&					operator =( const Panel& rhs );

		//
		const std::string&		Name() const			{return mName;}
		void					Show();
		void					Hide();
		bool					Visible() const			{return mShown;}
		bool					Toggle();
		void					Update();

		//
		typedef std::function< void ( void ) >		Callback_t;

		//////////////////////////////////////////////////////////////////////////
		struct Element_t
		{
			Element_t( Panel* owner ) : mpOwner( owner )		{}

			virtual void			Create( Panel& p )=0;
			virtual void			Update()=0;

			Panel*					mpOwner;
			Callback_t				mpCallback;
			bool					mReadOnly;
		};


		typedef std::shared_ptr< Element_t >		ElementPtr_t;
		typedef std::vector< ElementPtr_t >			ElementVector_t;

		// Create a panel type.
		static Panel					Create( const std::string& name );

		// Enumeration types.
		struct EnumType_t;
		struct EnumBuilder;

		static EnumBuilder				EnumType( const std::string name );

		// Structure types.
		struct StructType_t;
		template< typename Struct >
		struct StructBuilder;

		template< typename Struct >
		static StructBuilder< Struct >	StructType( const std::string name )
		{
			return Panel::StructBuilder< Struct >( name, sizeof( Struct ) );
		}


		//////////////////////////////////////////////////////////////////////////
		Panel&					Color( uint8_t r, uint8_t g, uint8_t b, uint8_t a=200 );
		Panel&					Callback( Callback_t cb );

		enum TextColor
		{
			Light,
			Dark
		};
		Panel&					DarkText();
		Panel&					LightText();


		//////////////////////////////////////////////////////////////////////////
		template< typename Type >
		struct DataType : public Element_t
		{
			typedef Type									ValueType_t;
			typedef std::function< Type () >				Getter_t;
			typedef std::function< void ( const Type& ) >	Setter_t;

			DataType( Panel* owner, const std::string& name, Type* value )
			:	Element_t( owner )
			,	mName( name )
			,	mpValue( value )
			,	mLastValue( *value )
			{}

			DataType( Panel* owner, const std::string& name, Getter_t g, Setter_t s )
			:	Element_t( owner )
			,	mName( name )
			,	mpValue( nullptr )
			,	mLastValue( g() )
			,	mpGetter( g )
			,	mpSetter( s )
			{
			}

			virtual void				Create( Panel& p ) override
			{
				if( mpValue )
				{
					TwType	itemType	=	Panel::ItemTypes_t< Type >::kItemType;
					TwAddVarRW( p._Bar(), mName.c_str(), itemType, mpValue, nullptr );
				}
				else
				{
					TwType	itemType	=	Panel::ItemTypes_t< Type >::kItemType;
					if( mpSetter )
					{
						TwAddVarCB(
							p._Bar(),
							mName.c_str(),
							itemType,
							[=]( const void* v, void* c )	{ ((DataType< Type >*)c)->mpSetter( *(const Type*)v ); },
							[=]( void* v, void* c )			{ *((Type*)v) = ((DataType< Type >*)c)->mpGetter(); },
							this, nullptr );
					}
					{
						TwAddVarCB(
							p._Bar(),
							mName.c_str(),
							itemType,
							nullptr,
							[=]( void* v, void* c )			{ *((Type*)v) = ((DataType< Type >*)c)->mpGetter(); },
							this, "readonly" );
					}
				}
			}
			virtual void				Update() override
			{
				if( mpValue && (*mpValue != mLastValue ) )
				{
					mLastValue	=	*mpValue;

					if( mpCallback )
						(mpCallback)();
				}
			}

			Panel&						End()
			{
				return *mpOwner;
			}
			DataType< Type >&			Callback( Panel::Callback_t cb )
			{
				mpCallback	=	cb;
				return *this;
			}

			std::string					mName;
			Type*						mpValue;
			Type						mLastValue;
			Getter_t					mpGetter;
			Setter_t					mpSetter;

		private:
			static void					TW_CALL _Set( const void* v, void* cd )
			{
			}
			/*
			typedef void (TW_CALL * TwSetVarCallback)(const void *value, void *clientData);
			typedef void (TW_CALL * TwGetVarCallback)(void *value, void *clientData);
			*/
		};

		template< typename Type >	struct ItemTypes_t;

		template< typename Type, const TwType TypeEnum >
		struct ItemTypes2_t : public ItemTypes_t< Type >
		{
			static const TwType kItemType	=	TypeEnum;
		};

		//////////////////////////////////////////////////////////////////////////
		template< typename Type >
		DataType< Type >&		Variable( const std::string& name, Type* );

		template< typename Type >
		DataType< Type >&		Variable( const std::string& name, typename DataType< Type >::Getter_t, typename DataType< Type >::Setter_t );

		//////////////////////////////////////////////////////////////////////////
		// Specialized types.
		struct					Color3f_t;
		struct					Separator_t;
		struct					Button_t;
		struct					Enumeration_t;
		struct					Struct_t;
		struct					String_t;

		//////////////////////////////////////////////////////////////////////////
		String_t&				String( const std::string& name, std::string* str );
		Color3f_t&				Color3f( const std::string& name, Math::Vector3f* color );
		Separator_t&			Separator( const std::string& name );
		Button_t&				Button( const std::string& name, Callback_t cb );
		Enumeration_t&			Enumeration( const std::string& name, EnumType_t et, int32_t* value );
		Struct_t&				Struct( const std::string& name, const StructType_t& type, void* data );

	private:
		//////////////////////////////////////////////////////////////////////////
		void					_CreatePanel();
		std::string				_ToName( const std::string& label );
		TwBar*					_Bar();
		void					_UpdateTweakables();

		//////////////////////////////////////////////////////////////////////////
		std::string				mName;
		uint8_t					mR, mG, mB, mA;
		TextColor				mTextColor;

		CONFIG_MEMBER( bool, mShown );
		CONFIG_MEMBER( Math::Point2i, mPosition );
		CONFIG_MEMBER( Math::Point2i, mSize );

		//////////////////////////////////////////////////////////////////////////
		TwBar*					mpPanel;
		ElementVector_t			mElements;
		Callback_t				mpCallback;
	};


	//////////////////////////////////////////////////////////////////////////
	template<>					struct Panel::ItemTypes_t< bool >				{static const TwType kItemType	=	ETwType::TW_TYPE_BOOLCPP;};
	template<>					struct Panel::ItemTypes_t< int8_t >				{static const TwType kItemType	=	ETwType::TW_TYPE_INT8;};
	template<>					struct Panel::ItemTypes_t< int16_t >			{static const TwType kItemType	=	ETwType::TW_TYPE_INT16;};
	template<>					struct Panel::ItemTypes_t< int32_t >			{static const TwType kItemType	=	ETwType::TW_TYPE_INT32;};
	template<>					struct Panel::ItemTypes_t< uint8_t >			{static const TwType kItemType	=	ETwType::TW_TYPE_UINT8;};
	template<>					struct Panel::ItemTypes_t< uint16_t >			{static const TwType kItemType	=	ETwType::TW_TYPE_UINT16;};
	template<>					struct Panel::ItemTypes_t< uint32_t >			{static const TwType kItemType	=	ETwType::TW_TYPE_UINT32;};
	template<>					struct Panel::ItemTypes_t< float >				{static const TwType kItemType	=	ETwType::TW_TYPE_FLOAT;};
	template<>					struct Panel::ItemTypes_t< double >				{static const TwType kItemType	=	ETwType::TW_TYPE_DOUBLE;};
	template<>					struct Panel::ItemTypes_t< Math::Vector3f >		{static const TwType kItemType	=	ETwType::TW_TYPE_DIR3F;};
	template<>					struct Panel::ItemTypes_t< Math::Quaternionf >	{static const TwType kItemType	=	ETwType::TW_TYPE_QUAT4F;};
	template<>					struct Panel::ItemTypes_t< std::string >		{static const TwType kItemType	=	ETwType::TW_TYPE_STDSTRING;};


	//////////////////////////////////////////////////////////////////////////
	struct Panel::EnumType_t
	{
						operator TwType() const		{return mEnumType;}
		TwType			mEnumType;
	};


	//////////////////////////////////////////////////////////////////////////
	struct Panel::EnumBuilder
	{
		EnumBuilder( const std::string& name );

						operator EnumType_t() const;
		EnumBuilder&	Entry( const std::string& name, uint32_t value=~1 );

		typedef std::pair< std::string, int32_t >	EnumEntry_t;
		typedef std::vector< EnumEntry_t >			EnumEntryVector_t;

		std::string			mName;
		EnumEntryVector_t	mEntries;
	};


	//////////////////////////////////////////////////////////////////////////
	struct Panel::StructType_t
	{
						operator TwType() const		{return mEnumType;}
		TwType			mEnumType;
	};


	//////////////////////////////////////////////////////////////////////////
	struct Panel::Struct_t : public Panel::Element_t
	{
		Struct_t( Panel* owner, const std::string& name, const StructType_t& st, void* d );

		virtual void			Create( Panel& p ) override;
		virtual void			Update() override;

		Panel&					End();

		std::string				mName;
		StructType_t			mType;
		void*					mpData;
	};


	//////////////////////////////////////////////////////////////////////////
	template< typename Struct >
	struct Panel::StructBuilder
	{
		typedef std::vector< TwStructMember >	MemberVector_t;

		StructBuilder( const std::string& name, size_t structSize )
		:	mName( name )
		,	mStructSize( structSize )
		{
		}

								operator StructType_t() const
		{
			StructType_t	result	=	{TwDefineStruct( mName.c_str(), mMembers.data(), mMembers.size(), mStructSize, nullptr, nullptr )};
			return result;
		}

		template< typename Type >
		StructBuilder&			Member( const std::string& name, size_t offset )
		{
			TwType			itemType	=	Panel::ItemTypes_t< Type >::kItemType;
			TwStructMember	member		=	{name.c_str(), itemType, offset, ""};
			mMembers.push_back( member );
			return *this;
		}

		std::string				mName;
		size_t					mStructSize;
		MemberVector_t			mMembers;
	};


	//////////////////////////////////////////////////////////////////////////
	struct Panel::String_t : public Panel::Element_t
	{
		String_t( Panel* owner, const std::string& name, std::string* str );

		virtual void			Create( Panel& p ) override;
		virtual void			Update() override;

		String_t&				Opened();
		Panel&					End();
		String_t&				Callback( Callback_t cb );

		std::string				mName;
		std::string*			mpString;
		bool					mOpened;
		std::string				mLastValue;
	};


	//////////////////////////////////////////////////////////////////////////
	struct Panel::Color3f_t : public Panel::Element_t
	{
		Color3f_t( Panel* owner, const std::string& name, Math::Vector3f* color );

		virtual void			Create( Panel& p ) override;
		virtual void			Update() override;

		Color3f_t&				Opened();
		Panel&					End();
		Color3f_t&				Callback( Callback_t cb );

		std::string				mName;
		Math::Vector3f*			mpColor;
		bool					mOpened;
		Math::Vector3f			mLastValue;
	};


	//////////////////////////////////////////////////////////////////////////
	struct Panel::Separator_t : public Panel::Element_t
	{
		Separator_t( Panel* owner, const std::string& name );

		virtual void			Create( Panel& p ) override;
		virtual void			Update() override								{}

		Panel&					End();

		std::string				mName;
	};


	//////////////////////////////////////////////////////////////////////////
	struct Panel::Button_t : public Panel::Element_t
	{
		Button_t( Panel* owner, const std::string& name, Callback_t cb );

		virtual void			Create( Panel& p ) override;
		virtual void			Update() override;
		Panel&					End();
		static void				Callback( void* cbd );

		std::string				mName;
	};


	//////////////////////////////////////////////////////////////////////////
	struct Panel::Enumeration_t : public Panel::Element_t
	{
		Enumeration_t( Panel* owner, const std::string& name, EnumType_t et, int32_t* value );

		virtual void			Create( Panel& p ) override;
		virtual void			Update() override;

		Panel&					End();
		Enumeration_t&			Callback( Callback_t cb );

		std::string				mName;
		TwType					mEnumType;
		int32_t*				mpValue;
		int32_t					mLastValue;
	};


	//////////////////////////////////////////////////////////////////////////
	template< typename Type > inline
	Panel::DataType< Type >&		Panel::Variable( const std::string& name, Type* v )
	{
		DataType< Type >*	result	=	new DataType< Type >( this, name, v );
		mElements.push_back( ElementPtr_t( result ) );
		return *result;
	}

	template< typename Type > inline
	Panel::DataType< Type >&		Panel::Variable( const std::string& name, typename DataType< Type >::Getter_t g, typename DataType< Type >::Setter_t s )
	{
		DataType< Type >*	result	=	new DataType< Type >( this, name, g, s );
		mElements.push_back( ElementPtr_t( result ) );
		return *result;
	}
}


#endif // ENABLE_DEVBUILD
